Merged [15063]: Don't send out typing notifications to group chats, since Gaim doesn...
[adiumx.git] / Plugins / Gaim Service / CBGaimAccount.m
blob043da5c9e847f5a8d4b4023db57e00cdb96b5822
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIAccountController.h"
18 #import "AIContactController.h"
19 #import "AIContentController.h"
20 #import "AIInterfaceController.h"
21 #import "AIStatusController.h"
22 #import "AIPreferenceController.h"
23 #import "CBGaimAccount.h"
24 #import "SLGaimCocoaAdapter.h"
25 #import <AIUtilities/AIAttributedStringAdditions.h>
26 #import <AIUtilities/AIDictionaryAdditions.h>
27 #import <AIUtilities/AIMenuAdditions.h>
28 #import <AIUtilities/AIMutableOwnerArray.h>
29 #import <AIUtilities/AIStringAdditions.h>
30 #import <AIUtilities/CBApplicationAdditions.h>
31 #import <AIUtilities/CBObjectAdditions.h>
32 #import <AIUtilities/ESImageAdditions.h>
33 #import <AIUtilities/ESSystemNetworkDefaults.h>
34 #import <Adium/AIAccount.h>
35 #import <Adium/AIChat.h>
36 #import <Adium/AIContentMessage.h>
37 #import <Adium/AIHTMLDecoder.h>
38 #import <Adium/AIListContact.h>
39 #import <Adium/AIListGroup.h>
40 #import <Adium/AIMetaContact.h>
41 #import <Adium/AIService.h>
42 #import <Adium/AIServiceIcons.h>
43 #import <Adium/AIStatus.h>
44 #import <Adium/ESFileTransfer.h>
45 #import <Adium/AIWindowController.h>
47 #define NO_GROUP                                                @"__NoGroup__"
49 #define AUTO_RECONNECT_DELAY            2.0     //Delay in seconds
50 #define RECONNECTION_ATTEMPTS           4
52 #define PREF_GROUP_ALIASES                      @"Aliases"              //Preference group to store aliases in
54 @interface CBGaimAccount (PRIVATE)
55 - (void)connect;
56 - (void)disconnect;
58 - (void)setBuddyImageFromFilename:(char *)imageFilename;
59 - (NSString *)_userIconCachePath;
60 - (void)_setInstantMessagesWithContact:(AIListContact *)contact enabled:(BOOL)enable;
62 - (NSString *)_mapIncomingGroupName:(NSString *)name;
63 - (NSString *)_mapOutgoingGroupName:(NSString *)name;
65 - (NSString *)displayServiceIDForUID:(NSString *)aUID;
67 //- (void)_updateAllEventsForBuddy:(GaimBuddy*)buddy;
68 - (void)setTypingFlagOfChat:(AIChat *)inChat to:(NSNumber *)typingState;
69 - (void)_updateAway:(AIListContact *)theContact toAway:(BOOL)newAway;
71 - (AIChat*)_openChatWithContact:(AIListContact *)contact andConversation:(GaimConversation*)conv;
73 - (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(GaimMessageFlags)flags date:(NSDate *)date;
74 - (void)_sentMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat toDestinationListContact:(AIListContact *)destinationContact flags:(GaimMessageFlags)flags date:(NSDate *)date;
75 - (NSString *)_processGaimImagesInString:(NSString *)inString;
76 - (NSString *)_handleFileSendsWithinMessage:(NSString *)inString toContact:(AIListContact *)listContact contentMessage:(AIContentMessage *)contentMessage;
77 - (NSString *)_messageImageCachePathForID:(int)imageID;
79 - (ESFileTransfer *)createFileTransferObjectForXfer:(GaimXfer *)xfer;
81 - (void)displayError:(NSString *)errorDesc;
82 - (NSNumber *)shouldCheckMail;
84 - (void)updateStatusForKey:(NSString *)key immediately:(BOOL)immediately;
86 - (void)configureGaimAccountNotifyingTarget:(id)target selector:(SEL)selector;
88 @end
90 @implementation CBGaimAccount
92 static BOOL didInitSSL = NO;
94 static SLGaimCocoaAdapter *gaimThread = nil;
96 // The GaimAccount currently associated with this Adium account
97 - (GaimAccount*)gaimAccount
99         //Create a gaim account if one does not already exist
100         if (!account) {
101                 [self createNewGaimAccount];
102                 GaimDebug(@"%x: created GaimAccount 0x%x with UID %@, protocolPlugin %s", [NSRunLoop currentRunLoop],account, [self UID], [self protocolPlugin]);
103         }
104         
105     return account;
108 - (SLGaimCocoaAdapter *)gaimThread
110         return gaimThread;
113 - (void)initSSL
115         if (!didInitSSL) {
116                 didInitSSL = gaim_init_ssl_openssl_plugin();
117         }
120 // Subclasses must override this
121 - (const char*)protocolPlugin { return NULL; }
123 // Contacts ------------------------------------------------------------------------------------------------
124 #pragma mark Contacts
125 - (oneway void)newContact:(AIListContact *)theContact withName:(NSString *)inName
130 - (oneway void)updateContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName
132         //A quick sign on/sign off can leave these messages in the threaded messaging queue... we most definitely don't want
133         //to put the contact back into a remote group after signing off, as a ghost will appear. Spooky!
134         if([self online] || [self integerStatusObjectForKey:@"Connecting"]){
135                 //When a new contact is created, if we aren't already silent and delayed, set it  a second to cover our initial
136                 //status updates
137                 if(!silentAndDelayed){
138                         [self silenceAllContactUpdatesForInterval:2.0];
139                         [[adium contactController] delayListObjectNotificationsUntilInactivity];                
140                 }
141                 
142                 //If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
143                 //This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
144                 if(![contactName isEqualToString:[theContact formattedUID]] && ![contactName isEqualToString:[theContact UID]]){
145                         [theContact setStatusObject:contactName
146                                                                  forKey:@"FormattedUID"
147                                                                  notify:NotifyLater];
148                 }
150                 if(groupName && [groupName isEqualToString:@GAIM_ORPHANS_GROUP_NAME]){
151                         [theContact setRemoteGroupName:AILocalizedString(@"Orphans","Name for the orphans group")];
152                 }else if(groupName && [groupName length] != 0){
153                         [theContact setRemoteGroupName:[self _mapIncomingGroupName:groupName]];
154                 }else{
155                         [theContact setRemoteGroupName:[self _mapIncomingGroupName:nil]];
156                 }
157                 
158                 [self gotGroupForContact:theContact];
159         }else{
160                 GaimDebug(@"Got %@ for %@ while not online",groupName,theContact);
161         }
165  * @brief Change the UID of a contact
167  * If we're just passed a formatted version of the current UID, don't change the UID but instead use the information
168  * as the FormattedUID.  For example, we get sent this when an AIM contact's name formatting changes; we always want
169  * to use a lowercase and space-free version for the UID, however.
170  */
171 - (void)renameContact:(AIListContact *)theContact toUID:(NSString *)newUID
173         //If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
174         //This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
175         NSString        *filteredUID = [[self service] filterUID:newUID removeIgnoredCharacters:YES];
176         
177         if ([filteredUID isEqualToString:[theContact UID]]) {
178                 [theContact setStatusObject:newUID
179                                                          forKey:@"FormattedUID"
180                                                          notify:NotifyLater];           
181         } else {
182                 [theContact setUID:newUID];             
183         }
186 - (void)applyAlias:(NSString *)gaimAlias toContact:(AIListContact *)theContact
187 {       
188         BOOL changes = NO;
189         BOOL displayNameChanges = NO;
191         //Store this alias as the serverside display name so long as it isn't identical when unformatted to the UID
192         if(![[gaimAlias compactedString] isEqualToString:[[theContact UID] compactedString]]){
193                 
194                 //This is the server display name.  Set it as such.
195                 if(![gaimAlias isEqualToString:[theContact statusObjectForKey:@"Server Display Name"]]){
196                         //Set the server display name status object as the full display name
197                         [theContact setStatusObject:gaimAlias
198                                                                  forKey:@"Server Display Name"
199                                                                  notify:NO];
200                         
201                         changes = YES;
202                 }
203                 
204                 //Use it either as the status message or the display name.
205                 if ([self useDisplayNameAsStatusMessage]){
206                         if (![[theContact stringFromAttributedStringStatusObjectForKey:@"ContactListStatusMessage"] isEqualToString:gaimAlias]){
207                                 [theContact setStatusObject:[[[NSAttributedString alloc] initWithString:gaimAlias] autorelease]
208                                                                          forKey:@"ContactListStatusMessage" 
209                                                                          notify:NO];
210                                 
211                                 changes = YES;
212                         }
213                         
214                 }else{
215                         AIMutableOwnerArray     *displayNameArray = [theContact displayArrayForKey:@"Display Name"];
216                         NSString                        *oldDisplayName = [displayNameArray objectValue];
217                         
218                         //If the mutableOwnerArray's current value isn't identical to this alias, we should set it
219                         if(![[displayNameArray objectWithOwner:self] isEqualToString:gaimAlias]){
220                                 [displayNameArray setObject:gaimAlias
221                                                                   withOwner:self
222                                                           priorityLevel:Low_Priority];
223                                 
224                                 //If this causes the object value to change, we need to request a manual update of the display name
225                                 if(oldDisplayName != [displayNameArray objectValue]){
226                                         displayNameChanges = YES;
227                                 }
228                         }
229                 }
230                 
231         }else{
232                 if(![gaimAlias isEqualToString:[theContact formattedUID]] && ![gaimAlias isEqualToString:[theContact UID]]){
233                         [theContact setStatusObject:gaimAlias
234                                                                  forKey:@"FormattedUID"
235                                                                  notify:NO];
236                         
237                         changes = YES;
238                 }
239         }
240         
241         if(changes){
242                 //Apply any changes
243                 [theContact notifyOfChangedStatusSilently:silentAndDelayed];
244         }
245         
246         if (displayNameChanges){
247                 //Notify of display name changes
248                 [[adium contactController] listObjectAttributesChanged:theContact
249                                                                                                   modifiedKeys:[NSSet setWithObject:@"Display Name"]];
250                 
251                 //XXX - There must be a cleaner way to do this alias stuff!  This works for now
252                 //Request an alias change
253                 [[adium notificationCenter] postNotificationName:Contact_ApplyDisplayName
254                                                                                                   object:theContact
255                                                                                                 userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
256                                                                                                                                                                          forKey:@"Notify"]];
257         }
260 - (void)updateContact:(AIListContact *)theContact toAlias:(NSString *)gaimAlias
262         [self applyAlias:gaimAlias toContact:theContact];
265 - (BOOL)useDisplayNameAsStatusMessage
267         return NO;
270 - (oneway void)updateContact:(AIListContact *)theContact forEvent:(NSNumber *)event
272 }               
275 //Signed online
276 - (oneway void)updateSignon:(AIListContact *)theContact withData:(void *)data
278         NSNumber *contactOnlineStatus = [theContact statusObjectForKey:@"Online"];
279         
280         if(!contactOnlineStatus || ([contactOnlineStatus boolValue] != YES)){
281                 [theContact setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Online" notify:NO];
282                 
283                 if(!silentAndDelayed){
284                         [theContact setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Signed On" notify:NO];
285                         [theContact setStatusObject:nil forKey:@"Signed Off" notify:NO];
286                         [theContact setStatusObject:nil forKey:@"Signed On" afterDelay:15];
287                 }
289                 //Apply any changes
290                 [theContact notifyOfChangedStatusSilently:silentAndDelayed];
291         }
294 //Signed offline
295 - (oneway void)updateSignoff:(AIListContact *)theContact withData:(void *)data
297         NSNumber *contactOnlineStatus = [theContact statusObjectForKey:@"Online"];
298         if(contactOnlineStatus && ([contactOnlineStatus boolValue] != NO)){             
299                 if(!silentAndDelayed){
300                         [theContact setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Signed Off" notify:NO];
301                         [theContact setStatusObject:nil forKey:@"Signed On" notify:NO];                 
302                         [theContact setStatusObject:nil forKey:@"Signed Off" afterDelay:15];
303                 }
305                 //Will also apply any changes applied above, so no need to call notifyOfChangedStatusSilently 
306                 [self removeStatusObjectsFromContact:theContact silently:silentAndDelayed];
307         }
310 //Signon Time
311 - (oneway void)updateSignonTime:(AIListContact *)theContact withData:(NSDate *)signonDate
312 {       
313         if (signonDate) {
314                 //Set the signon time
315                 [theContact setStatusObject:signonDate
316                                                          forKey:@"Signon Date"
317                                                          notify:NO];
318                 
319                 //Apply any changes
320                 [theContact notifyOfChangedStatusSilently:silentAndDelayed];
321         }
325  * @brief Status name to use for a Gaim buddy
327  * Called by SLGaimCocoaAdapter on the gaim thread
328  */
329 - (NSString *)statusNameForGaimBuddy:(GaimBuddy *)b
331         return nil;
335  * @brief Status message for a contact
337  * Called by SLGaimCocoaAdapter on the gaim thread
338  */
339 - (NSAttributedString *)statusMessageForGaimBuddy:(GaimBuddy *)b
341         return nil;
345  * @brief Update the status message and away state of the contact
347  *  Called by SLGaimCocoaAdapter on the main thread
348  */
349 - (void)updateStatusForContact:(AIListContact *)theContact toStatusType:(NSNumber *)statusTypeNumber statusName:(NSString *)statusName statusMessage:(NSAttributedString *)statusMessage
351         [theContact setStatusWithName:statusName
352                                            statusType:[statusTypeNumber intValue]
353                                                    notify:NotifyLater];
354         [theContact setStatusMessage:statusMessage
355                                                   notify:NotifyLater];
356         
357         //Apply the change
358         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
362 * @brief Update the status state of the contact
364  *  Called by SLGaimCocoaAdapter on the main thread
365  */
366 - (void)updateStatusForContact:(AIListContact *)theContact toStatusType:(NSNumber *)statusTypeNumber statusName:(NSString *)statusName
368         [theContact setStatusWithName:statusName
369                                            statusType:[statusTypeNumber intValue]
370                                                    notify:NotifyLater];
371         
372         //Apply the change
373         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
377  * @brief Update the status message of the contact
379  *  Called by SLGaimCocoaAdapter on the main thread
380  */
381 - (void)updateStatusForContact:(AIListContact *)theContact toStatusMessage:(NSAttributedString *)statusMessage
383         [theContact setStatusMessage:statusMessage
384                                                   notify:NotifyLater];
385         
386         //Apply the change
387         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
390 //Idle time
391 - (void)updateWentIdle:(AIListContact *)theContact withData:(NSDate *)idleSinceDate
393         if (idleSinceDate){
394                 [theContact setStatusObject:idleSinceDate
395                                                          forKey:@"IdleSince"
396                                                          notify:NO];
397         }else{
398                 //No idleSinceDate means we are Idle but don't know how long, so set to -1
399                 [theContact setStatusObject:[NSNumber numberWithInt:-1]
400                                                          forKey:@"Idle"
401                                                          notify:NO];
402         }
404         //@"Idle", for a contact with an IdleSince date, will be changing every minute.  @"IsIdle" provides observers a way
405         //to perform an action when the contact becomes/comes back from idle, regardless of whether an IdleSince is available,
406         //without having to do that action every minute for other contacts.
407         [theContact setStatusObject:[NSNumber numberWithBool:YES]
408                                                  forKey:@"IsIdle"
409                                                  notify:NO];
410         
411         //Apply any changes
412         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
414 - (void)updateIdleReturn:(AIListContact *)theContact withData:(void *)data
416         [theContact setStatusObject:nil
417                                                  forKey:@"IdleSince"
418                                                  notify:NO];
419         [theContact setStatusObject:nil
420                                                  forKey:@"Idle"
421                                                  notify:NO];
422         
423         [theContact setStatusObject:nil
424                                                  forKey:@"IsIdle"
425                                                  notify:NO];
426         
427         //Apply any changes
428         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
430         
431 //Evil level (warning level)
432 - (oneway void)updateEvil:(AIListContact *)theContact withData:(NSNumber *)evilNumber
434         //Set the warning level or clear it if it's now 0.
435         int evil = [evilNumber intValue];
436         NSNumber *currentWarningLevel = [theContact statusObjectForKey:@"Warning"];
438         if (evil > 0){
439                 if (!currentWarningLevel || ([currentWarningLevel intValue] != evil)) {
440                         [theContact setStatusObject:evilNumber
441                                                                  forKey:@"Warning"
442                                                                  notify:NO];
443                         //Apply any changes
444                         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
445                 }
446         }else{
447                 if (currentWarningLevel) {
448                         [theContact setStatusObject:nil
449                                                                  forKey:@"Warning" 
450                                                                  notify:NO];
451                         //Apply any changes
452                         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
454                 }
455         }
456 }   
458 //Buddy Icon
459 - (oneway void)updateIcon:(AIListContact *)theContact withData:(NSData *)userIconData
461         if (userIconData){
462                 //Observers get a single shot at utilizing the user icon data in its raw form
463                 [theContact setStatusObject:userIconData forKey:@"UserIconData" notify:NO];
464                 
465                 //Set the User Icon as an NSImage
466                 NSImage *userIcon = [[NSImage alloc] initWithData:userIconData];
467                 [theContact setStatusObject:userIcon forKey:KEY_USER_ICON notify:NO];
468                 [userIcon release];
469                 
470                 //Apply any changes
471                 [theContact notifyOfChangedStatusSilently:silentAndDelayed];
472                 
473                 //Clear the UserIconData
474                 [theContact setStatusObject:nil forKey:@"UserIconData" notify:NO];
475         }
478 - (oneway void)updateUserInfo:(AIListContact *)theContact withData:(NSString *)userInfoString
480         NSString *oldUserInfoString = [theContact statusObjectForKey:@"TextProfileString"];
481         
482         if (userInfoString && [userInfoString length]) {
483                 if (![userInfoString isEqualToString:oldUserInfoString]) {
484                         
485                         [theContact setStatusObject:userInfoString
486                                                                  forKey:@"TextProfileString" 
487                                                                  notify:NO];
488                         [theContact setStatusObject:[AIHTMLDecoder decodeHTML:userInfoString]
489                                                                  forKey:@"TextProfile" 
490                                                                  notify:NO];
491                 }
492         } else if (oldUserInfoString) {
493                 [theContact setStatusObject:nil forKey:@"TextProfileString" notify:NO];
494                 [theContact setStatusObject:nil forKey:@"TextProfile" notify:NO];       
495         }       
496         
497         //Apply any changes
498         [theContact notifyOfChangedStatusSilently:silentAndDelayed];
502  * @brief Gaim removed a contact from the local blist
504  * This can happen in many situations:
505  *      - For every contact on an account when the account signs off
506  *      - For a contact as it is deleted by the user
507  *      - For a contact as it is deleted by Gaim (e.g. when Sametime refuses an addition because it is known to be invalid)
508  *      - In the middle of the move process as a contact moves from one group to another
510  * We need not take any action; we'll be notified of changes by Gaim as necessary.
511  */
512 - (oneway void)removeContact:(AIListContact *)theContact
517 //To allow root level buddies on protocols which don't support them, we map any buddies in a group
518 //named after this account's UID to the root group.  These functions handle the mapping.  Group names should
519 //be filtered through incoming before being sent to Adium - and group names from Adium should be filtered through
520 //outgoing before being used.
521 - (NSString *)_mapIncomingGroupName:(NSString *)name
523         if(!name || ([[name compactedString] caseInsensitiveCompare:[self UID]] == 0)){
524                 return(ADIUM_ROOT_GROUP_NAME);
525         }else{
526                 return(name);
527         }
529 - (NSString *)_mapOutgoingGroupName:(NSString *)name
531         if([[name compactedString] caseInsensitiveCompare:ADIUM_ROOT_GROUP_NAME] == 0){
532                 return([self UID]);
533         }else{
534                 return(name);
535         }
538 //Update the status of a contact (Request their profile)
539 - (void)delayedUpdateContactStatus:(AIListContact *)inContact
540 {       
541     //Request profile
542         AILog(@"%@: Update %@ : %i %i",self,inContact,[inContact online],[inContact isStranger]);
543 //    if ([inContact online] || [inContact isStranger]){
544                 [gaimThread getInfoFor:[inContact UID] onAccount:self];
545 //    }
548 - (oneway void)requestAddContactWithUID:(NSString *)contactUID
550         [[adium contactController] requestAddContactWithUID:contactUID
551                                                                                                 service:[self _serviceForUID:contactUID]];
554 - (AIService *)_serviceForUID:(NSString *)contactUID
556         return([self service]);
559 - (void)gotGroupForContact:(AIListContact *)listContact {};
561 /*********************/
562 /* AIAccount_Handles */
563 /*********************/
564 #pragma mark Contact List Editing
566 - (void)removeContacts:(NSArray *)objects
568         NSEnumerator    *enumerator = [objects objectEnumerator];
569         AIListContact   *object;
570         
571         while(object = [enumerator nextObject]){
572                 NSString        *groupName = [self _mapOutgoingGroupName:[object remoteGroupName]];
574                 //Have the gaim thread perform the serverside actions
575                 [gaimThread removeUID:[object UID] onAccount:self fromGroup:groupName];
576                 
577                 //Remove it from Adium's list
578                 [object setRemoteGroupName:nil];
579         }
582 - (void)addContacts:(NSArray *)objects toGroup:(AIListGroup *)inGroup
584         NSEnumerator    *enumerator = [objects objectEnumerator];
585         AIListContact   *object;
586         NSString                *groupName = [self _mapOutgoingGroupName:[inGroup UID]];
587         
588         while(object = [enumerator nextObject]){
589                 [gaimThread addUID:[self _UIDForAddingObject:object] onAccount:self toGroup:groupName];
590                 
591                 //Add it to Adium's list
592                 [object setRemoteGroupName:[inGroup UID]]; //Use the non-mapped group name locally
593         }
596 - (NSString *)_UIDForAddingObject:(AIListContact *)object
598         return([object UID]);
601 - (void)moveListObjects:(NSArray *)objects toGroup:(AIListGroup *)group
603         NSString                *groupName = [self _mapOutgoingGroupName:[group UID]];
604         NSEnumerator    *enumerator;
605         AIListContact   *listObject;
606         
607         //Move the objects to it
608         enumerator = [objects objectEnumerator];
609         while(listObject = [enumerator nextObject]){
610                 if([listObject isKindOfClass:[AIListGroup class]]){
611                         //Since no protocol here supports nesting, a group move is really a re-name
612                         
613                 }else{
614                         //                      NSString        *oldGroupName = [self _mapOutgoingGroupName:[listObject remoteGroupName]];
615                         
616                         //Tell the gaim thread to perform the serverside operation
617                         [gaimThread moveUID:[listObject UID] onAccount:self toGroup:groupName];
619                         //Use the non-mapped group name locally
620                         [listObject setRemoteGroupName:[group UID]];
621                 }
622         }               
625 - (void)renameGroup:(AIListGroup *)inGroup to:(NSString *)newName
627         NSString                *groupName = [self _mapOutgoingGroupName:[inGroup UID]];
629         //Tell the gaim thread to perform the serverside operation      
630         [gaimThread renameGroup:groupName onAccount:self to:newName];
632         //We must also update the remote grouping of all our contacts in that group
633         NSEnumerator    *enumerator = [[[adium contactController] allContactsInGroup:inGroup subgroups:YES onAccount:self] objectEnumerator];
634         AIListContact   *contact;
635         
636         while(contact = [enumerator nextObject]){
637                 //Evan: should we use groupName or newName here?
638                 [contact setRemoteGroupName:newName];
639         }
642 - (void)deleteGroup:(AIListGroup *)inGroup
644         NSString                *groupName = [self _mapOutgoingGroupName:[inGroup UID]];
646         [gaimThread deleteGroup:groupName onAccount:self];
649 // Return YES if the contact list is editable
650 - (BOOL)contactListEditable
652     return([[self statusObjectForKey:@"Online"] boolValue]);
655 //Chats ------------------------------------------------------------
656 #pragma mark Chats
658 //Add a new chat - this will ultimately call -(BOOL)openChat:(AIChat *)chat below.
659 - (oneway void)addChat:(AIChat *)chat
661         //Open the chat
662         [[adium contentController] openChat:chat];
665 //Open a chat for Adium
666 - (BOOL)openChat:(AIChat *)chat
668         /* The #if 0'd block below causes crashes in msn_tooltip_text() on MSN */
669 #if 0
670         AIListContact   *listContact;
671         
672         //Obtain the contact's information if it's a stranger
673         if ((listContact = [chat listObject]) && ([listContact isStranger])){
674                 [self delayedUpdateContactStatus:listContact];
675         }
676 #endif
677         
678         AILog(@"gaim openChat:%@ for %@",chat,[chat uniqueChatID]);
680         //Inform gaim that we have opened this chat
681         [gaimThread openChat:chat onAccount:self];
682         
683         //Created the chat successfully
684         return(YES);
687 - (BOOL)closeChat:(AIChat*)chat
689         [gaimThread closeChat:chat];
690         
691         //Be sure any remaining typing flag is cleared as the chat closes
692         [self setTypingFlagOfChat:chat to:nil];
693         AILog(@"gaim closeChat:%@",[chat uniqueChatID]);
694         
695     return YES;
698 - (AIChat *)mainThreadChatWithContact:(AIListContact *)contact
700         AIChat *chat;
702         //First, make sure the chat is created
703         [[adium contentController] mainPerformSelector:@selector(chatWithContact:)
704                                                                                 withObject:contact
705                                                                          waitUntilDone:YES];
707         //Now return the existing chat
708         chat = [[adium contentController] existingChatWithContact:contact];
710         return chat;
713 - (AIChat *)mainThreadChatWithName:(NSString *)name
715         AIChat *chat;
717         /*
718          First, make sure the chat is created - we will get here from a call in which Gaim has already
719          created the GaimConversation, so there's no need for a chatCreationInfo dictionary.
720          */
721         
722         [[adium contentController] mainPerformSelector:@selector(chatWithName:onAccount:chatCreationInfo:)
723                                                                                 withObject:name
724                                                                                 withObject:self
725                                                                                 withObject:nil
726                                                                          waitUntilDone:YES];
727         
728         //Now return the existing chat
729         chat = [[adium contentController] existingChatWithName:name onAccount:self];
730         
731         return chat;
734 //Typing update in an IM
735 - (oneway void)typingUpdateForIMChat:(AIChat *)chat typing:(NSNumber *)typingState
737         [self setTypingFlagOfChat:chat
738                                                    to:typingState];
741 //Multiuser chat update
742 - (oneway void)convUpdateForChat:(AIChat *)chat type:(NSNumber *)type
746 - (oneway void)updateTopic:(NSString *)inTopic forChat:(AIChat *)chat
748         
750 - (oneway void)updateTitle:(NSString *)inTitle forChat:(AIChat *)chat
752         [[chat displayArrayForKey:@"Display Name"] setObject:inTitle
753                                                                                            withOwner:self];
756 - (oneway void)updateForChat:(AIChat *)chat type:(NSNumber *)type
758         AIChatUpdateType        updateType = [type intValue];
759         NSString                        *key = nil;
760         switch (updateType){
761                 case AIChatTimedOut:
762                         if ([self displayConversationTimedOut]){
763                                 key = KEY_CHAT_TIMED_OUT;
764                         }
765                         break;
766                         
767                 case AIChatClosedWindow:
768                         if ([self displayConversationClosed]){
769                                 key = KEY_CHAT_CLOSED_WINDOW;
770                         }
771                         break;
772         }
773         
774         if (key){
775                 [chat setStatusObject:[NSNumber numberWithBool:YES] forKey:key notify:NotifyNow];
776                 [chat setStatusObject:nil forKey:key notify:NotifyNever];
777                 
778         }
781 - (oneway void)errorForChat:(AIChat *)chat type:(NSNumber *)type
783         [chat setStatusObject:type forKey:KEY_CHAT_ERROR notify:NotifyNow];
784         [chat setStatusObject:nil forKey:KEY_CHAT_ERROR notify:NotifyNever];
787 - (oneway void)receivedIMChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
789         GaimMessageFlags                flags = [[messageDict objectForKey:@"GaimMessageFlags"] intValue];
790         NSAttributedString              *attributedMessage;
791         AIListContact                   *listContact;
792         NSDate                                  *date;
793         
794         attributedMessage = [messageDict objectForKey:@"AttributedMessage"];
795         listContact = [chat listObject];
796         date = [messageDict objectForKey:@"Date"];
797         
798         if ((flags & GAIM_MESSAGE_SEND) != 0) {
799         //Gaim is telling us that our message was sent successfully.            
801                 //We can now tell the other side that we're done typing
802                 //[gaimThread sendTyping:AINotTyping inChat:chat];
803     }else{
804                 
805                 //Clear the typing flag of the chat since a message was just received
806                 [self setTypingFlagOfChat:chat to:nil];
807                 
808                 [self _receivedMessage:attributedMessage
809                                                 inChat:chat 
810                            fromListContact:listContact
811                                                  flags:flags
812                                                   date:date];
813         }
816 - (oneway void)receivedMultiChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
817 {       
818         GaimMessageFlags                flags = [[messageDict objectForKey:@"GaimMessageFlags"] intValue];
819         NSAttributedString              *attributedMessage;
820         NSDate                                  *date;
821         
822         attributedMessage = [messageDict objectForKey:@"AttributedMessage"];
823         date = [messageDict objectForKey:@"Date"];
824         
825         if ((flags & GAIM_MESSAGE_SEND) != 0){
826         //Gaim is telling us that our message was sent successfully.            
828                 //We can now tell the other side that we're done typing
829                 //[gaimThread sendTyping:AINotTyping inChat:chat];
830                 
831         }else{
832                 NSString                        *source = [messageDict objectForKey:@"Source"];
834                 //We display the message locally when it is sent.  If the protocol sends the message back to us, we should
835                 //simply ignore it (MSN does this when a display name is set, for example).
836                 if (![source isEqualToString:[self UID]]){
837                         AIListContact   *listContact;
838                         
839                         //source may be (null) for system messages like topic changes
840                         listContact = (source ? [self contactWithUID:source] : nil);
842                         if(listContact){
843                                 [self _receivedMessage:attributedMessage
844                                                                 inChat:chat 
845                                            fromListContact:listContact
846                                                                  flags:flags
847                                                                   date:date];
848                         }else{
849                                 //If we didn't get a listContact, this is a gaim status message... display it as such.
850                                 [[adium contentController] displayStatusMessage:[attributedMessage string]
851                                                                                                                 ofType:@"gaim"
852                                                                                                                 inChat:chat];
854                         }
855                 }
856         }
859 - (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(GaimMessageFlags)flags date:(NSDate *)date
860 {               
861         AIContentMessage *messageObject = [AIContentMessage messageInChat:chat
862                                                                                                                    withSource:sourceContact
863                                                                                                                   destination:self
864                                                                                                                                  date:date
865                                                                                                                           message:attributedMessage
866                                                                                                                         autoreply:(flags & GAIM_MESSAGE_AUTO_RESP) != 0];
867         
868         [[adium contentController] receiveContentObject:messageObject];
871 /*********************/
872 /* AIAccount_Content */
873 /*********************/
874 #pragma mark Content
875 - (BOOL)sendContentObject:(AIContentObject*)object
877     BOOL            sent = NO;
878         
879         if (gaim_account_is_connected(account)) {
880                 if([[object type] isEqualToString:CONTENT_MESSAGE_TYPE]) {
881                         AIContentMessage        *contentMessage = (AIContentMessage*)object;
882                         AIChat                          *chat = [contentMessage chat];
883                         NSAttributedString  *message = [contentMessage message];
884                         NSString                        *encodedMessage;
885                         
886                         //Grab the list object (which may be null if this isn't a chat with a particular listObject)
887                         AIListContact           *listObject = [chat listObject];
888                         
889                         //Use GaimConvImFlags for now; multiuser chats will end up ignoring this
890                         GaimConvImFlags         flags = ([contentMessage isAutoreply] ? GAIM_CONV_IM_AUTO_RESP : 0);
891                         
892                         //If this connection doesn't support new lines, send all lines before newlines as separate messages
893                         if (account->gc->flags & GAIM_CONNECTION_NO_NEWLINES) {
894                                 NSRange         endlineRange;
895                                 NSRange         returnRange;
896                                 
897                                 while(((endlineRange = [[message string] rangeOfString:@"\n"]).location) != NSNotFound ||
898                                           ((returnRange = [[message string] rangeOfString:@"\r"]).location) != NSNotFound){
899                                         
900                                         //Use whichever endline character is found first
901                                         NSRange operativeRange = ((endlineRange.location < returnRange.location) ? endlineRange : returnRange);
902                                         
903                                         if (operativeRange.location > 0){
904                                                 NSAttributedString  *thisPart;
905                                                 NSString                        *thisPartString;
906                                                 
907                                                 thisPart = [message attributedSubstringFromRange:NSMakeRange(0,operativeRange.location-1)];
908                                                 thisPartString = [thisPart string];
909                                                 
910                                                 encodedMessage = [self encodedAttributedString:thisPart
911                                                                                                                  forListObject:listObject
912                                                                                                                 contentMessage:contentMessage];
913                                                 if (encodedMessage){
914                                                         //Check for the AdiumFT tag indicating an embedded file transfer.
915                                                         //Only deal with scanning deeper if it's found.
916                                                         if ([encodedMessage rangeOfString:@"<AdiumFT "
917                                                                                                           options:NSCaseInsensitiveSearch].location != NSNotFound){
918                                                                 encodedMessage = [self _handleFileSendsWithinMessage:encodedMessage
919                                                                                                                                                    toContact:listObject
920                                                                                                                                           contentMessage:contentMessage];
921                                                         }
922                                                         
923                                                         sent = [gaimThread sendEncodedMessage:encodedMessage
924                                                                                    originalMessage:thisPartString
925                                                                                            fromAccount:self
926                                                                                                         inChat:chat
927                                                                                                  withFlags:flags];
928                                                 }
929                                         }
930                                         
931                                         message = [message attributedSubstringFromRange:NSMakeRange(operativeRange.location+operativeRange.length,[[message string] length]-operativeRange.location)];
932                                 }
933                                 
934                         }
935                         
936                         if ([message length]){
937                                 encodedMessage = [self encodedAttributedString:message
938                                                                                                  forListObject:listObject
939                                                                                                 contentMessage:contentMessage];
940                                 if (encodedMessage){
941                                         NSString        *messageString;
942                                         
943                                         //Check for the AdiumFT tag indicating an embedded file transfer.
944                                         //Only deal with scanning deeper if it's found.
945                                         if ([encodedMessage rangeOfString:@"<AdiumFT "
946                                                                                           options:NSCaseInsensitiveSearch].location != NSNotFound){
947                                                 encodedMessage = [self _handleFileSendsWithinMessage:encodedMessage
948                                                                                                                                    toContact:listObject
949                                                                                                                           contentMessage:contentMessage];
950                                         }
951                                         
952                                         messageString = [message string];
953                                         
954                                         sent = [gaimThread sendEncodedMessage:encodedMessage
955                                                                                   originalMessage:messageString
956                                                                                           fromAccount:self
957                                                                                                    inChat:chat
958                                                                                                 withFlags:flags];
959                                 }
960                         }
962                 } else if ([[object type] isEqualToString:CONTENT_TYPING_TYPE]) {
963                         AIContentTyping *contentTyping = (AIContentTyping*)object;
964                         AIChat *chat = [contentTyping chat];
966                         if (![chat name]) {
967                                 [gaimThread sendTyping:[contentTyping typingState] inChat:chat];
968                         }
970                         sent = YES;
971                 }
972         }
973         
974     return(sent);
977 //Return YES if we're available for sending the specified content or will be soon (are currently connecting).
978 //If inListObject is nil, we can return YES if we will 'most likely' be able to send the content.
979 - (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact
981     BOOL        weAreOnline = [self online];
982         
983     if([inType isEqualToString:CONTENT_MESSAGE_TYPE]){
984         if((weAreOnline && (inContact == nil || [inContact online])) ||
985                    ([self integerStatusObjectForKey:@"Connecting"])){ 
986                         return(YES);
987         }
988     }else if (([inType isEqualToString:FILE_TRANSFER_TYPE]) && ([self conformsToProtocol:@protocol(AIAccount_Files)])){
989                 if(weAreOnline){
990                         if(inContact){
991                                 if([inContact online]){
992                                         return([self allowFileTransferWithListObject:inContact]);
993                                 }
994                         }else{
995                                 return(YES);
996                         }
997        }        
998         }
999         
1000     return(NO);
1003 - (BOOL)allowFileTransferWithListObject:(AIListObject *)inListObject
1005         return YES;
1008 - (NSString *)_handleFileSendsWithinMessage:(NSString *)inString toContact:(AIListContact *)listContact contentMessage:(AIContentMessage *)contentMessage
1010         if (listContact){
1011                 NSScanner                       *scanner;
1012                 NSCharacterSet          *tagCharStart, *tagEnd, *absoluteTagEnd;
1013                 NSString                        *chunkString;
1014                 NSMutableString         *processedString;
1015                 
1016                 tagCharStart = [NSCharacterSet characterSetWithCharactersInString:@"<"];
1017                 tagEnd = [NSCharacterSet characterSetWithCharactersInString:@" >"];
1018                 absoluteTagEnd = [NSCharacterSet characterSetWithCharactersInString:@">"];
1019                 
1020                 scanner = [NSScanner scannerWithString:inString];
1021                 [scanner setCaseSensitive:NO];
1022                 [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]];
1023                 
1024                 processedString = [[NSMutableString alloc] init];
1025                 
1026                 //Parse the HTML
1027                 while(![scanner isAtEnd]){
1028                         //Find an HTML IMG tag
1029                         if([scanner scanUpToString:@"<AdiumFT" intoString:&chunkString]){
1030                                 [processedString appendString:chunkString];
1031                         }
1032                         
1033                         //Process the tag
1034                         if([scanner scanCharactersFromSet:tagCharStart intoString:nil]){ //If a tag wasn't found, we don't process.
1035                                                                                                                                                          //            unsigned scanLocation = [scanner scanLocation]; //Remember our location (if this is an invalid tag we'll need to move back)
1036                                 
1037                                 //Get the tag itself
1038                                 if([scanner scanUpToCharactersFromSet:tagEnd intoString:&chunkString]){
1039                                         
1040                                         if([chunkString caseInsensitiveCompare:@"AdiumFT"] == 0){
1041                                                 if([scanner scanUpToCharactersFromSet:absoluteTagEnd intoString:&chunkString]){
1042                                                         
1043                                                         //Extract the file we wish to send
1044                                                         NSDictionary    *imgArguments = [AIHTMLDecoder parseArguments:chunkString];
1045                                                         NSString                *filePath = [[imgArguments objectForKey:@"src"] stringByUnescapingFromHTML];
1046                                                         
1047                                                         //Send the file
1048                                                         [[adium fileTransferController] sendFile:filePath toListContact:listContact];
1049                                                 }
1050                                         }
1051                                         
1052                                         if (![scanner isAtEnd]){
1053                                                 [scanner setScanLocation:[scanner scanLocation]+1];
1054                                         }
1055                                 }
1056                         }
1057                 }
1058                 
1059                 /* We've removed AdiumFT tags from an arbitrarily encoded HTML string.  This could leave us with no actual
1060                  * text to send.  If that's the case, we don't want to return something like <HTML></HTML>. Instead, we want
1061                  * to return nil. We therefore decode and reencode our new string. */
1063                 return ([self encodedAttributedString:[AIHTMLDecoder decodeHTML:processedString]
1064                                                                 forListObject:listContact
1065                                                            contentMessage:contentMessage]);
1066         }else{
1067                 GaimDebug(@"Sending a file to a chat.  Are you insane?");
1068                 return (inString);
1069         }
1072 // **XXX** Not used at present. Do we want to?
1073 - (BOOL)shouldSendAutoresponsesWhileAway
1075         if (account && account->gc){
1076                 return (account->gc->flags & GAIM_CONNECTION_AUTO_RESP);
1077         }
1078         
1079         return NO;
1082 #pragma mark GaimConversation User Lists
1083 - (void)addUser:(NSString *)contactName toChat:(AIChat *)chat newArrival:(NSNumber *)newArrival
1085         AIListContact *listContact;
1086         
1087         if ((chat) &&
1088                 (listContact = [self contactWithUID:contactName])){
1090                 if (!namesAreCaseSensitive){
1091                         [listContact setStatusObject:contactName forKey:@"FormattedUID" notify:NotifyNow];
1092                 }
1094                 [chat addParticipatingListObject:listContact notify:(newArrival && [newArrival boolValue])];
1095         }
1098 - (oneway void)addUsersArray:(NSArray *)usersArray toChat:(AIChat *)chat
1100         NSEnumerator    *enumerator;
1101         NSString                *contactName;
1102         
1103         GaimDebug(@"*** %@: addUsersArray:%@ toChat:%@",self,usersArray,chat);
1105         enumerator = [usersArray objectEnumerator];
1106         while ((contactName = [enumerator nextObject])) {
1107                 [self addUser:contactName toChat:chat newArrival:nil];
1108         }
1111 - (oneway void)removeUser:(NSString *)contactName fromChat:(AIChat *)chat
1113         AIListContact   *contact;
1115         if ((chat) && 
1116                 (contact = [self contactWithUID:contactName])){
1117                 
1118                 [chat removeParticipatingListObject:contact];
1119                 
1120                 GaimDebug(@"%@ removeUser:%@ fromChat:%@",self,contact,chat);
1121         }       
1124 - (oneway void)removeUsersArray:(NSArray *)usersArray fromChat:(AIChat *)chat
1126         NSEnumerator    *enumerator = [usersArray objectEnumerator];
1127         NSString                *contactName;
1128         while(contactName = [enumerator nextObject]){
1129                 [self removeUser:contactName fromChat:chat];
1130         }
1133 /*********************/
1134 /* AIAccount_Privacy */
1135 /*********************/
1136 #pragma mark Privacy
1137 - (BOOL)addListObject:(AIListObject *)inObject toPrivacyList:(PRIVACY_TYPE)type
1139     if (type == PRIVACY_PERMIT)
1140         return (gaim_privacy_permit_add(account,[[inObject UID] UTF8String],FALSE));
1141     else
1142         return (gaim_privacy_deny_add(account,[[inObject UID] UTF8String],FALSE));
1145 - (BOOL)removeListObject:(AIListObject *)inObject fromPrivacyList:(PRIVACY_TYPE)type
1147     if (type == PRIVACY_PERMIT)
1148         return (gaim_privacy_permit_remove(account,[[inObject UID] UTF8String],FALSE));
1149     else
1150         return (gaim_privacy_deny_remove(account,[[inObject UID] UTF8String],FALSE));
1153 - (NSArray *)listObjectsOnPrivacyList:(PRIVACY_TYPE)type
1155         return (type == PRIVACY_PERMIT ? permittedContactsArray : deniedContactsArray);
1158 - (NSArray *)listObjectIDsOnPrivacyList:(PRIVACY_TYPE)type
1160         NSArray *listObjectArray = [self listObjectsOnPrivacyList:type];
1161         NSMutableArray *idArray =  [[NSMutableArray alloc] initWithCapacity:[listObjectArray count]];
1162         NSEnumerator *enumerator = [listObjectArray objectEnumerator];
1163         AIListObject *object = nil;
1164         
1165         while(object = [enumerator nextObject]){
1166                 [idArray addObject:[object UID]];
1167         }
1168         
1169         return [idArray autorelease];
1172 - (oneway void)privacyPermitListAdded:(NSString *)sourceUID
1174         [self accountPrivacyList:PRIVACY_PERMIT added:sourceUID];
1177 - (oneway void)privacyDenyListAdded:(NSString *)sourceUID
1179         [self accountPrivacyList:PRIVACY_DENY added:sourceUID];
1182 - (void)accountPrivacyList:(PRIVACY_TYPE)type added:(NSString *)sourceUID
1184         //Can't really trust sourceUID to not be @"" or something silly like that
1185         if ([sourceUID length]){
1186                 //Get our contact
1187                 AIListContact   *contact = [self contactWithUID:sourceUID];
1188                 
1189                 [(type == PRIVACY_PERMIT ? permittedContactsArray : deniedContactsArray) addObject:contact];
1190         }
1193 - (oneway void)privacyPermitListRemoved:(NSString *)sourceUID
1195         [self accountPrivacyList:PRIVACY_PERMIT removed:sourceUID];
1198 - (oneway void)privacyDenyListRemoved:(NSString *)sourceUID
1200         [self accountPrivacyList:PRIVACY_DENY removed:sourceUID];
1203 - (void)accountPrivacyList:(PRIVACY_TYPE)type removed:(NSString *)sourceUID
1205         //Can't really trust sourceUID to not be @"" or something silly like that
1206         if ([sourceUID length]){
1207                 if (!namesAreCaseSensitive){
1208                         sourceUID = [sourceUID compactedString];
1209                 }
1210                 
1211                 //Get our contact, which must already exist for us to care about its removal
1212                 AIListContact   *contact = [[adium contactController] existingContactWithService:service
1213                                                                                                                                                                  account:self
1214                                                                                                                                                                          UID:sourceUID];
1215                 
1216                 if (contact){
1217                         [(type == PRIVACY_PERMIT ? permittedContactsArray : deniedContactsArray) removeObject:contact];
1218                 }
1219         }
1222 - (void)setPrivacyOptions:(PRIVACY_OPTION)option
1224         if (account && gaim_account_get_connection(account)) {
1225                 GaimPrivacyType privacyType;
1227                 switch(option){
1228                         case PRIVACY_ALLOW_ALL:
1229                         default:
1230                                 privacyType = GAIM_PRIVACY_ALLOW_ALL;
1231                                 break;
1232                         case PRIVACY_DENY_ALL:
1233                                 privacyType = GAIM_PRIVACY_DENY_ALL;
1234                                 break;
1235                         case PRIVACY_ALLOW_USERS:
1236                                 privacyType = GAIM_PRIVACY_ALLOW_USERS;
1237                                 break;
1238                         case PRIVACY_DENY_USERS:
1239                                 privacyType = GAIM_PRIVACY_DENY_USERS;
1240                                 break;
1241                         case PRIVACY_ALLOW_CONTACTLIST:
1242                                 privacyType = GAIM_PRIVACY_ALLOW_BUDDYLIST;
1243                                 break;
1244                         
1245                 }
1246                 account->perm_deny = privacyType;
1247                 serv_set_permit_deny(gaim_account_get_connection(account));
1248         } else {
1249                 AILog(@"Couldn't set privacy options for %@ (%x %x)",self,account,gaim_account_get_connection(account));
1250         }
1253 - (PRIVACY_OPTION)privacyOptions
1255         PRIVACY_OPTION privacyOption = -1;
1256         
1257         if(account){
1258                 GaimPrivacyType privacyType = account->perm_deny;
1259                 
1260                 switch(privacyType){
1261                         case GAIM_PRIVACY_ALLOW_ALL:
1262                         default:
1263                                 privacyOption = PRIVACY_ALLOW_ALL;
1264                                 break;
1265                         case GAIM_PRIVACY_DENY_ALL:
1266                                 privacyOption = PRIVACY_DENY_ALL;
1267                                 break;
1268                         case GAIM_PRIVACY_ALLOW_USERS:
1269                                 privacyOption = PRIVACY_ALLOW_USERS;
1270                                 break;
1271                         case GAIM_PRIVACY_DENY_USERS:
1272                                 privacyOption = PRIVACY_DENY_USERS;
1273                                 break;
1274                         case GAIM_PRIVACY_ALLOW_BUDDYLIST:
1275                                 privacyOption = PRIVACY_ALLOW_CONTACTLIST;
1276                                 break;
1277                 }
1278         }
1280         return(privacyOption);
1283 /*****************************************************/
1284 /* File transfer / AIAccount_Files inherited methods */
1285 /*****************************************************/
1286 #pragma mark File Transfer
1288 //Create a protocol-specific xfer object, set it up as requested, and begin sending
1289 - (void)_beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
1291         GaimXfer *xfer = [self newOutgoingXferForFileTransfer:fileTransfer];
1292         
1293         if (xfer){
1294                 //Associate the fileTransfer and the xfer with each other
1295                 [fileTransfer setAccountData:[NSValue valueWithPointer:xfer]];
1296                 xfer->ui_data = [fileTransfer retain];
1297                 
1298                 //Set the filename
1299                 gaim_xfer_set_local_filename(xfer, [[fileTransfer localFilename] UTF8String]);
1300                 gaim_xfer_set_filename(xfer, [[[fileTransfer localFilename] lastPathComponent] UTF8String]);
1301                 
1302                 /*
1303                  Request that the transfer begins.
1304                  We will be asked to accept it via:
1305                         - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
1306                  below.
1307                  */
1308                 [gaimThread xferRequest:xfer];
1309         }
1311 //By default, protocols can not create GaimXfer objects
1312 - (GaimXfer *)newOutgoingXferForFileTransfer:(ESFileTransfer *)fileTransfer
1314         return nil;
1317 /* 
1318  * @brief The account requested that we received a file.
1320  * Set up the ESFileTransfer and query the fileTransferController for a save location.
1321  * 
1322  * @result The window controller for the window prompting the user to take action, or nil if no prompt was shown
1323  */
1324 - (NSWindowController *)requestReceiveOfFileTransfer:(ESFileTransfer *)fileTransfer
1326         GaimDebug(@"File transfer request received: %@",fileTransfer);
1327     return [[adium fileTransferController] receiveRequestForFileTransfer:fileTransfer];
1330 //Create an ESFileTransfer object from an xfer
1331 - (ESFileTransfer *)newFileTransferObjectWith:(NSString *)destinationUID
1332                                                                                  size:(unsigned long long)inSize
1333                                                            remoteFilename:(NSString *)remoteFilename
1335         return([self mainPerformSelector:@selector(_mainThreadNewFileTransferObjectWith:size:remoteFilename:)
1336                                                   withObject:destinationUID
1337                                                   withObject:[NSNumber numberWithUnsignedLongLong:inSize]
1338                                                   withObject:remoteFilename
1339                                                  returnValue:YES]);
1341 - (ESFileTransfer *)_mainThreadNewFileTransferObjectWith:(NSString *)destinationUID
1342                                                                                                         size:(NSNumber *)inSize
1343                                                                                   remoteFilename:remoteFilename
1345         AIListContact   *contact = [self contactWithUID:destinationUID];
1346     ESFileTransfer      *fileTransfer;
1347         
1348         fileTransfer = [[adium fileTransferController] newFileTransferWithContact:contact
1349                                                                                                                                    forAccount:self]; 
1350         [fileTransfer setSize:[inSize unsignedLongLongValue]];
1351         [fileTransfer setRemoteFilename:remoteFilename];
1353     return(fileTransfer);
1356 //Update an ESFileTransfer object progress
1357 - (oneway void)updateProgressForFileTransfer:(ESFileTransfer *)fileTransfer percent:(NSNumber *)percent bytesSent:(NSNumber *)bytesSent
1359         float percentDone = [percent floatValue];
1360     [fileTransfer setPercentDone:percentDone bytesSent:[bytesSent unsignedLongValue]];
1363 //The local side canceled the transfer.  We probably already have this status set, but set it just in case.
1364 - (oneway void)fileTransferCanceledLocally:(ESFileTransfer *)fileTransfer
1366         [fileTransfer setStatus:Canceled_Local_FileTransfer];
1369 //The remote side canceled the transfer, the fool. Update our status.
1370 - (oneway void)fileTransferCanceledRemotely:(ESFileTransfer *)fileTransfer
1372         [fileTransfer setStatus:Canceled_Remote_FileTransfer];
1375 - (oneway void)destroyFileTransfer:(ESFileTransfer *)fileTransfer
1377         GaimDebug(@"Destroy file transfer %@",fileTransfer);
1378         [fileTransfer release];
1381 //Accept a send or receive ESFileTransfer object, beginning the transfer.
1382 //Subsequently inform the fileTransferController that the fun has begun.
1383 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
1385     GaimDebug(@"Accepted file transfer %@",fileTransfer);
1386         
1387         GaimXfer                *xfer;
1388         GaimXferType    xferType;
1389         
1390         xfer = [[fileTransfer accountData] pointerValue];
1392     xferType = gaim_xfer_get_type(xfer);
1393     if ( xferType == GAIM_XFER_SEND ) {
1394         [fileTransfer setType:Outgoing_FileTransfer];   
1395     } else if ( xferType == GAIM_XFER_RECEIVE ) {
1396         [fileTransfer setType:Incoming_FileTransfer];
1397                 [fileTransfer setSize:(xfer->size)];
1398     }
1399     
1400     //accept the request
1401         [gaimThread xferRequestAccepted:xfer withFileName:[fileTransfer localFilename]];
1402     
1403         //set the size - must be done after request is accepted?
1405         
1406         [fileTransfer setStatus:Accepted_FileTransfer];
1409 //User refused a receive request.  Tell gaim; we don't release the ESFileTransfer object
1410 //since that will happen when the xfer is destroyed.  This will end up calling back on
1411 //- (oneway void)fileTransferCanceledLocally:(ESFileTransfer *)fileTransfer
1412 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
1414         GaimXfer        *xfer = [[fileTransfer accountData] pointerValue];
1415         if (xfer) {
1416                 [gaimThread xferRequestRejected:xfer];
1417         }
1420 //Cancel a file transfer in progress.  Tell gaim; we don't release the ESFileTransfer object
1421 //since that will happen when the xfer is destroyed.  This will end up calling back on
1422 //- (oneway void)fileTransferCanceledLocally:(ESFileTransfer *)fileTransfer
1423 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
1425         GaimXfer        *xfer = [[fileTransfer accountData] pointerValue];
1426         if (xfer) {
1427                 [gaimThread xferCancel:xfer];
1428         }       
1431 //Account Connectivity -------------------------------------------------------------------------------------------------
1432 #pragma mark Connect
1433 //Connect this account (Our password should be in the instance variable 'password' all ready for us)
1434 - (void)connect
1436         if (!account) {
1437                 //create a gaim account if one does not already exist
1438                 [self createNewGaimAccount];
1439                 GaimDebug(@"created GaimAccount 0x%x with UID %@, protocolPlugin %s", account, [self UID], [self protocolPlugin]);
1440         }
1441         
1442         //We are connecting
1443         [self setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Connecting" notify:NotifyNow];
1444         
1445         //Make sure our settings are correct
1446         [self configureGaimAccountNotifyingTarget:self selector:@selector(continueConnectWithConfiguredGaimAccount)];
1449 - (void)continueConnectWithConfiguredGaimAccount
1451         //Configure libgaim's proxy settings; continueConnectWithConfiguredProxy will be called once we are ready
1452         [self configureAccountProxyNotifyingTarget:self selector:@selector(continueConnectWithConfiguredProxy)];
1455 - (void)continueConnectWithConfiguredProxy
1457         //Set password and connect
1458         gaim_account_set_password(account, [password UTF8String]);
1460         GaimDebug(@"Adium: Connect: %@ initiating connection.",[self UID]);
1462         [gaimThread connectAccount:self];
1464         GaimDebug(@"Adium: Connect: %@ done initiating connection %x.",[self UID], account->gc);
1468 //Make sure our settings are correct; notify target/selector when we're finished
1469 - (void)configureGaimAccountNotifyingTarget:(id)target selector:(SEL)selector
1471         NSInvocation    *contextInvocation;
1472         
1473         //Perform the synchronous configuration activities (subclasses may want to take action in this function)
1474         [self configureGaimAccount];
1475         
1476         contextInvocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
1477         
1478         [contextInvocation setTarget:target];
1479         [contextInvocation setSelector:selector];
1480         [contextInvocation retainArguments];
1482         //Set the text profile BEFORE beginning the connect process, to avoid problems with setting it while the
1483         //connect occurs. Once that's done, contextInvocation will be invoked, continuing the configureGaimAccount process.
1484         [self autoRefreshingOutgoingContentForStatusKey:@"TextProfile" 
1485                                                                                    selector:@selector(setAccountProfileTo:configureGaimAccountContext:)
1486                                                                                         context:contextInvocation];
1489 //Synchronous gaim account configuration activites, always performed after an account is created.
1490 //This is a definite subclassing point so prpls can apply their own account settings.
1491 - (void)configureGaimAccount
1493         NSString        *hostName;
1494         int                     portNumber;
1496         //Host (server)
1497         hostName = [self host];
1498         if (hostName && [hostName length]){
1499                 gaim_account_set_string(account, "server", [hostName UTF8String]);
1500         }
1501         
1502         //Port
1503         portNumber = [self port];
1504         if (portNumber){
1505                 gaim_account_set_int(account, "port", portNumber);
1506         }
1507         
1508         /*
1509          XXX: This is a hack for 0.8. Since we don't have a full privacy UI yet, we automatically set our privacy setting to
1510          the best one to use.
1511         */
1512         [gaimThread setDefaultPermitDenyForAccount:self];
1513         
1514         //E-mail checking
1515         gaim_account_set_check_mail(account, [[self shouldCheckMail] boolValue]);
1516         
1517         //Update a few status keys before we begin connecting.  Libgaim will send these automatically
1518     [self updateStatusForKey:KEY_USER_ICON];
1521 //Configure libgaim's proxy settings using the current system values
1522 - (void)configureAccountProxyNotifyingTarget:(id)target selector:(SEL)selector
1524         GaimProxyInfo           *proxy_info;
1525         GaimProxyType           gaimAccountProxyType;
1526         
1527         NSNumber                        *proxyPref = [self preferenceForKey:KEY_ACCOUNT_PROXY_TYPE group:GROUP_ACCOUNT_STATUS];
1528         BOOL                            proxyEnabled = [[self preferenceForKey:KEY_ACCOUNT_PROXY_ENABLED group:GROUP_ACCOUNT_STATUS] boolValue];
1530         NSString                        *host = nil;
1531         NSString                        *proxyUserName = nil;
1532         NSString                        *proxyPassword = nil;
1533         AdiumProxyType          proxyType;
1534         int                                     port = 0;
1535         NSInvocation            *invocation; 
1536         
1537         //Configure the invocation we will use when we are done configuring
1538         invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
1539         [invocation setSelector:selector];
1540         [invocation setTarget:target];
1541                 
1542         proxy_info = gaim_proxy_info_new();
1543         gaim_account_set_proxy_info(account, proxy_info);
1544         
1545         proxyType = (proxyPref ? [proxyPref intValue] : Adium_Proxy_Default_SOCKS5);
1546         
1547         if(!proxyEnabled){
1548                 //No proxy
1549                 gaim_proxy_info_set_type(proxy_info, GAIM_PROXY_NONE);
1550                 GaimDebug(@"Adium: Connect: %@ Connecting with no proxy.",[self UID]);
1551                 [invocation invoke];
1552                 
1553         }else if ((proxyType == Adium_Proxy_Default_SOCKS5) || 
1554                           (proxyType == Adium_Proxy_Default_HTTP) || 
1555                           (proxyType == Adium_Proxy_Default_SOCKS4)) {
1556                 //Load and use systemwide proxy settings
1557                 NSDictionary *systemProxySettingsDictionary;
1558                 ProxyType adiumProxyType = Proxy_None;
1559                 
1560                 if (proxyType == Adium_Proxy_Default_SOCKS5){
1561                         gaimAccountProxyType = GAIM_PROXY_SOCKS5;
1562                         adiumProxyType = Proxy_SOCKS5;
1563                         
1564                 }else if (proxyType == Adium_Proxy_Default_HTTP){
1565                         gaimAccountProxyType = GAIM_PROXY_HTTP;
1566                         adiumProxyType = Proxy_HTTP;
1567                         
1568                 }else if (proxyType == Adium_Proxy_Default_SOCKS4){
1569                                 gaimAccountProxyType = GAIM_PROXY_SOCKS4;
1570                                 adiumProxyType = Proxy_SOCKS4;
1571                 }
1572                 
1573                 GaimDebug(@"Loading proxy dictionary.");
1574                 
1575                 if((systemProxySettingsDictionary = [ESSystemNetworkDefaults systemProxySettingsDictionaryForType:adiumProxyType])) {
1577                         GaimDebug(@"Retrieved %@",systemProxySettingsDictionary);
1579                         host = [systemProxySettingsDictionary objectForKey:@"Host"];
1580                         port = [[systemProxySettingsDictionary objectForKey:@"Port"] intValue];
1581                         
1582                         proxyUserName = [systemProxySettingsDictionary objectForKey:@"Username"];
1583                         proxyPassword = [systemProxySettingsDictionary objectForKey:@"Password"];
1584                         
1585                 }else{
1586                         //Using system wide defaults, and no proxy of the specified type is set in the system preferences
1587                         gaimAccountProxyType = GAIM_PROXY_NONE;
1588                 }
1589                 
1590                 gaim_proxy_info_set_type(proxy_info, gaimAccountProxyType);
1591                 
1592                 gaim_proxy_info_set_host(proxy_info, (char *)[host UTF8String]);
1593                 gaim_proxy_info_set_port(proxy_info, port);
1594                 
1595                 if (proxyUserName && [proxyUserName length]){
1596                         gaim_proxy_info_set_username(proxy_info, (char *)[proxyUserName UTF8String]);
1597                         if (proxyPassword && [proxyPassword length]){
1598                                 gaim_proxy_info_set_password(proxy_info, (char *)[proxyPassword UTF8String]);
1599                         }
1600                 }
1601                 
1602                 GaimDebug(@"Systemwide proxy settings: %i %s:%i %s",proxy_info->type,proxy_info->host,proxy_info->port,proxy_info->username);
1603                 
1604                 [invocation invoke];
1606         }else{
1607                 host = [self preferenceForKey:KEY_ACCOUNT_PROXY_HOST group:GROUP_ACCOUNT_STATUS];
1608                 port = [[self preferenceForKey:KEY_ACCOUNT_PROXY_PORT group:GROUP_ACCOUNT_STATUS] intValue];
1609                 
1610                 switch (proxyType){
1611                         case Adium_Proxy_HTTP:
1612                                 gaimAccountProxyType = GAIM_PROXY_HTTP;
1613                                 break;
1614                         case Adium_Proxy_SOCKS4:
1615                                 gaimAccountProxyType = GAIM_PROXY_SOCKS4;
1616                                 break;
1617                         case Adium_Proxy_SOCKS5:
1618                                 gaimAccountProxyType = GAIM_PROXY_SOCKS5;
1619                                 break;
1620                         default:
1621                                 gaimAccountProxyType = GAIM_PROXY_NONE;
1622                                 break;
1623                 }
1624                 
1625                 gaim_proxy_info_set_type(proxy_info, gaimAccountProxyType);
1626                 gaim_proxy_info_set_host(proxy_info, (char *)[host UTF8String]);
1627                 gaim_proxy_info_set_port(proxy_info, port);
1628                 
1629                 //If we need to authenticate, request the password and finish setting up the proxy in gotProxyServerPassword:context:
1630                 proxyUserName = [self preferenceForKey:KEY_ACCOUNT_PROXY_USERNAME group:GROUP_ACCOUNT_STATUS];
1631                 if (proxyUserName && [proxyUserName length]){
1632                         gaim_proxy_info_set_username(proxy_info, (char *)[proxyUserName UTF8String]);
1633                         
1634                         [[adium accountController] passwordForProxyServer:host 
1635                                                                                                          userName:proxyUserName 
1636                                                                                           notifyingTarget:self 
1637                                                                                                          selector:@selector(gotProxyServerPassword:context:)
1638                                                                                                           context:invocation];
1639                 }else{
1640                         
1641                         GaimDebug(@"Adium proxy settings: %i %s:%i",proxy_info->type,proxy_info->host,proxy_info->port);
1642                         [invocation invoke];
1643                 }
1644         }
1647 //Retried the proxy password from the keychain
1648 - (void)gotProxyServerPassword:(NSString *)inPassword context:(NSInvocation *)invocation
1650         GaimProxyInfo           *proxy_info = gaim_account_get_proxy_info(account);
1651         
1652         if (inPassword){
1653                 gaim_proxy_info_set_password(proxy_info, (char *)[inPassword UTF8String]);
1654                 
1655                 GaimDebug(@"GotPassword: Proxy settings: %i %s:%i %s",proxy_info->type,proxy_info->host,proxy_info->port,proxy_info->username);
1657                 [invocation invoke];
1659         }else{
1660                 gaim_proxy_info_set_username(proxy_info, NULL);
1661                 
1662                 //We are no longer connecting
1663                 [self setStatusObject:nil forKey:@"Connecting" notify:NotifyNow];
1664         }
1667 //Sublcasses should override to provide a string for each progress step
1668 - (NSString *)connectionStringForStep:(int)step { return nil; };
1670 //Our account has connected
1671 - (oneway void)accountConnectionConnected
1673         AILog(@"************ %@ CONNECTED ***********",[self UID]);
1674         
1675         [self didConnect];
1676         
1677     //Silence updates
1678     [self silenceAllContactUpdatesForInterval:18.0];
1679         [[adium contactController] delayListObjectNotificationsUntilInactivity];
1680         
1681     //Reset reconnection attempts
1682     reconnectAttemptsRemaining = RECONNECTION_ATTEMPTS;
1684         //Clear any previous disconnection error
1685         [lastDisconnectionError release]; lastDisconnectionError = nil;
1688 - (oneway void)accountConnectionProgressStep:(NSNumber *)step percentDone:(NSNumber *)connectionProgressPrecent
1690         NSString        *connectionProgressString = [self connectionStringForStep:[step intValue]];
1692         [self setStatusObject:connectionProgressString forKey:@"ConnectionProgressString" notify:NO];
1693         [self setStatusObject:connectionProgressPrecent forKey:@"ConnectionProgressPercent" notify:NO]; 
1695         //Apply any changes
1696         [self notifyOfChangedStatusSilently:NO];
1697         
1698         AILog(@"************ %@ --step-- %i",[self UID],[step intValue]);
1701 - (void)createNewGaimAccount
1703         //Create a fresh version of the account
1704     account = gaim_account_new([[self formattedUID] UTF8String], [self protocolPlugin]);
1705         account->perm_deny = GAIM_PRIVACY_DENY_USERS;
1707         [self finishCreateNewGaimAccount];
1710 - (void)finishCreateNewGaimAccount
1712         if (!gaimThread){
1713                 gaimThread = [[SLGaimCocoaAdapter sharedInstance] retain];      
1714         }       
1715         
1716         [gaimThread addAdiumAccount:self];
1719 #pragma mark Disconnect
1722  * @brief Disconnect this account
1723  */
1724 - (void)disconnect
1726         if ([self online] || [self integerStatusObjectForKey:@"Connecting"]){
1727                 //As per AIAccount's documentation, call super's implementation
1728                 [super disconnect];
1730                 [self setStatusObject:nil forKey:@"Connecting" notify:NO];
1731                 [self setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Disconnecting" notify:NotifyNow];
1732                 [[adium contactController] delayListObjectNotificationsUntilInactivity];
1734                 //Tell libgaim to disconnect
1735                 [gaimThread disconnectAccount:self];
1736         }
1740  * @brief Our account was unexpectedly disconnected with an error message
1741  */
1742 - (oneway void)accountConnectionReportDisconnect:(NSString *)text
1744         //Retain the error message locally for use in -[CBGaimAccount accountConnectionDisconnected]
1745         if (lastDisconnectionError != text) {
1746                 [lastDisconnectionError release];
1747                 lastDisconnectionError = [text retain];
1748         }
1750         //We are disconnecting
1751     [self setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Disconnecting" notify:NotifyNow];
1752         
1753         GaimDebug(@"%@ reported disconnecting: %@",[self UID],lastDisconnectionError);
1756 - (oneway void)accountConnectionNotice:(NSString *)connectionNotice
1758     [[adium interfaceController] handleErrorMessage:[NSString stringWithFormat:AILocalizedString(@"%@ (%@) : Connection Notice",nil),[self formattedUID],[service description]]
1759                                     withDescription:connectionNotice];
1764  * @brief Our account has disconnected
1766  * This is called after the accoutn disconnects for any reason
1767  */
1768 - (oneway void)accountConnectionDisconnected
1770         BOOL                    connectionIsSuicidal = ((account && account->gc) ? account->gc->wants_to_die : NO);
1772     //We are now offline
1773         [self setStatusObject:nil forKey:@"Disconnecting" notify:NO];
1774         [self setStatusObject:nil forKey:@"Connecting" notify:NO];
1775         [self setStatusObject:nil forKey:@"Online" notify:NO];
1776         
1777         //Clear status objects which don't make sense for a disconnected account
1778         [self setStatusObject:nil forKey:@"TextProfile" notify:NO];
1779         
1780         //Apply any changes
1781         [self notifyOfChangedStatusSilently:NO];
1782         
1783         //If we were disconnected unexpectedly, attempt a reconnect. Give subclasses a chance to handle the disconnection error.
1784         //connectionIsSuicidal == TRUE when Gaim thinks we shouldn't attempt a reconnect.
1785         if([[self preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]/* && lastDisconnectionError*/){
1786                 if (reconnectAttemptsRemaining && 
1787                         [self shouldAttemptReconnectAfterDisconnectionError:&lastDisconnectionError] && !(connectionIsSuicidal)) {
1788                         
1789                         [self autoReconnectAfterDelay:AUTO_RECONNECT_DELAY];
1790                         reconnectAttemptsRemaining--;
1791                 }else{
1792                         if (lastDisconnectionError){
1793                                 //Display then clear the last disconnection error
1794                                 [self displayError:lastDisconnectionError];
1795                                 [lastDisconnectionError release]; lastDisconnectionError = nil;
1796                         }
1797                         
1798                         //Reset reconnection attempts
1799                         reconnectAttemptsRemaining = RECONNECTION_ATTEMPTS;
1800                         
1801                         //Clear our desire to be online.
1802                         [self setPreference:nil
1803                                                  forKey:@"Online"
1804                                                   group:GROUP_ACCOUNT_STATUS];
1805                 }
1806         }
1807         
1808         //Report that we disconnected
1809         [self didDisconnect];
1812 //By default, always attempt to reconnect.  Subclasses may override this to manage reconnect behavior.
1813 - (BOOL)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError
1815         return YES;
1818 #pragma mark Registering
1819 - (void)performRegisterWithPassword:(NSString *)inPassword
1821         //Save the new password
1822         if(password != inPassword){
1823                 [password release]; password = [inPassword retain];
1824         }
1825         
1826         if (!account) {
1827                 //create a gaim account if one does not already exist
1828                 [self createNewGaimAccount];
1829                 GaimDebug(@"Registering: created GaimAccount 0x%x with UID %@, protocolPlugin %s", account, [self UID], [self protocolPlugin]);
1830         }
1831         
1832         //We are connecting
1833         [self setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Connecting" notify:NotifyNow];
1834         
1835         //Make sure our settings are correct
1836         [self configureGaimAccountNotifyingTarget:self selector:@selector(continueRegisterWithConfiguredGaimAccount)];
1839 - (void)continueRegisterWithConfiguredGaimAccount
1841         //Configure libgaim's proxy settings; continueConnectWithConfiguredProxy will be called once we are ready
1842         [self configureAccountProxyNotifyingTarget:self selector:@selector(continueRegisterWithConfiguredProxy)];
1845 - (void)continueRegisterWithConfiguredProxy
1847         //Set password and connect
1848         gaim_account_set_password(account, [password UTF8String]);
1849         
1850         GaimDebug(@"Adium: Register: %@ initiating connection.",[self UID]);
1851         
1852         [gaimThread registerAccount:self];
1855 //Account Status ------------------------------------------------------------------------------------------------------
1856 #pragma mark Account Status
1857 //Status keys this account supports
1858 - (NSSet *)supportedPropertyKeys
1860         static NSMutableSet *supportedPropertyKeys = nil;
1861         
1862         if (!supportedPropertyKeys){
1863                 supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects:
1864                         @"Online",
1865                         @"Offline",
1866                         @"IdleSince",
1867                         @"IdleManuallySet",
1868                         @"TextProfile",
1869                         @"DefaultUserIconFilename",
1870                         KEY_ACCOUNT_CHECK_MAIL,
1871                         nil];
1872                 [supportedPropertyKeys unionSet:[super supportedPropertyKeys]];
1873                 
1874         }
1876         return supportedPropertyKeys;
1879 //Update our status
1880 - (void)updateStatusForKey:(NSString *)key
1881 {    
1882         [super updateStatusForKey:key];
1883         
1884     //Now look at keys which only make sense if we have an account
1885         if(account){
1886                 GaimDebug(@"%@: Updating status for key: %@",self, key);
1888                 if([key isEqualToString:@"IdleSince"]){
1889                         NSDate  *idleSince = [self preferenceForKey:@"IdleSince" group:GROUP_ACCOUNT_STATUS];
1890                         [self setAccountIdleSinceTo:idleSince];
1891                                                         
1892                 }else if([key isEqualToString:@"TextProfile"]){
1893                         [self autoRefreshingOutgoingContentForStatusKey:key selector:@selector(setAccountProfileTo:)];
1894                         
1895                 }else if([key isEqualToString:KEY_USER_ICON]){
1896                         NSData  *data = [self preferenceForKey:KEY_USER_ICON group:GROUP_ACCOUNT_STATUS];                       
1898                         [self setAccountUserImageData:data];
1900                 }else if([key isEqualToString:KEY_ACCOUNT_CHECK_MAIL]){
1901                         //Update the mail checking setting if the account is already made (if it isn't, we'll set it when it is made)
1902                         if(account){
1903                                 [gaimThread setCheckMail:[self shouldCheckMail]
1904                                                           forAccount:self];
1905                         }
1906                 }
1907         }
1911  * @brief Perform the setting of a status state
1913  * Sets the account to a passed status state.  The account should set itself to best possible status given the return
1914  * values of statusState's accessors.  The passed statusMessage has been filtered; it should be used rather than
1915  * [statusState statusMessage], which returns an unfiltered statusMessage.
1917  * @param statusState The state to enter
1918  * @param statusMessage The filtered status message to use.
1919  */
1920 - (void)setStatusState:(AIStatus *)statusState usingStatusMessage:(NSAttributedString *)statusMessage
1922         if([self online]){
1923                 char                            *gaimStatusType;
1924                 NSString                        *encodedStatusMessage;
1925                 
1926                 //Get the gaim status type from this class or subclasses, which may also potentially modify or nullify our statusMessage
1927                 gaimStatusType = [self gaimStatusTypeForStatus:statusState
1928                                                                                            message:&statusMessage];
1929                 
1930                 //Encode the status message if we still have one
1931                 encodedStatusMessage = (statusMessage ? 
1932                                                                 [self encodedAttributedString:statusMessage
1933                                                                                         forGaimStatusType:gaimStatusType]  :
1934                                                                 nil);
1936                 [self setStatusState:statusState withGaimStatusType:gaimStatusType andMessage:encodedStatusMessage];            
1937         }
1941  * @brief Return the gaim status type to be used for a status
1943  * Active services provided nonlocalized status names.  An AIStatus is passed to this method along with a pointer
1944  * to the status message.  This method should handle any status whose statusNname this service set as well as any statusName
1945  * defined in  AIStatusController.h (which will correspond to the services handled by Adium by default).
1946  * It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at
1947  * [statusState statusType] for a general idea of the status's type.
1949  * @param statusState The status for which to find the gaim status equivalent
1950  * @param statusMessage A pointer to the statusMessage.  Set *statusMessage to nil if it should not be used directly for this status.
1952  * @result The gaim status equivalent
1953  */
1954 - (char *)gaimStatusTypeForStatus:(AIStatus *)statusState
1955                                                   message:(NSAttributedString **)statusMessage
1957         AIStatusType    statusType = [statusState statusType];
1958         char                    *gaimStatusType = NULL;
1959         
1960         /* CBGaimAccount just handles available and away in the most simple way possible; 
1961          * we don't even care what the statusName is. */
1962         switch(statusType){
1963                 case AIAvailableStatusType:
1964                         gaimStatusType = "Available";
1965                         break;
1966                 case AIAwayStatusType:
1967                 case AIInvisibleStatusType: /* Invisible defaults to just being an away status */
1968                         gaimStatusType = GAIM_AWAY_CUSTOM;
1969                         //If we make it here, and we don't have a status message, generate one from the status controller's description.
1970                         if((*statusMessage == nil) || ([*statusMessage length] == 0)){
1971                                 *statusMessage = [NSAttributedString stringWithString:[[adium statusController] descriptionForStateOfStatus:statusState]];
1972                         }
1973                         break;
1974                 case AIOfflineStatusType:
1975                         //I'm really unsure how we actually get here with AIOfflineStatusType, but ensure this function doesn't return NULL
1976                         gaimStatusType = "";
1977                         break;
1978         }
1979         
1980         return gaimStatusType;
1984  * @brief Perform the actual setting a state
1986  * This is called by setStatusState.  It allows subclasses to perform any other behaviors, such as modifying a display
1987  * name, which are called for by the setting of the state; most of the processing has already been done, however, so
1988  * most subclasses will not need to implement this.
1990  * @param statusState The AIStatus which is being set
1991  * @param gaimStatusType The status type which will be passed to Gaim, or NULL if Gaim's status will not be set for this account
1992  * @param statusMessage A properly encoded message which will be associated with the status if possible.
1993  */
1994 - (void)setStatusState:(AIStatus *)statusState withGaimStatusType:(const char *)gaimStatusType andMessage:(NSString *)statusMessage
1996         [gaimThread setGaimStatusType:gaimStatusType 
1997                                           withMessage:statusMessage
1998                                                 onAccount:self];
2001 //Set our idle (Pass nil for no idle)
2002 - (void)setAccountIdleSinceTo:(NSDate *)idleSince
2004         [gaimThread setIdleSinceTo:idleSince onAccount:self];
2005         
2006         //We now should update our idle status object
2007         [self setStatusObject:([idleSince timeIntervalSinceNow] ? idleSince : nil)
2008                                    forKey:@"IdleSince"
2009                                    notify:NotifyNow];
2012 //Set the profile, then invoke the passed invocation to return control to the target/selector specified
2013 //by a configureGaimAccountNotifyingTarget:selector: call.
2014 - (void)setAccountProfileTo:(NSAttributedString *)profile configureGaimAccountContext:(NSInvocation *)inInvocation
2016         [self setAccountProfileTo:profile];
2017         
2018         [inInvocation invoke];
2021 //Set our profile immediately on the gaimThread
2022 - (void)setAccountProfileTo:(NSAttributedString *)profile
2024         if(!profile || ![[profile string] isEqualToString:[[self statusObjectForKey:@"TextProfile"] string]]){
2025                 NSString        *profileHTML = nil;
2026                 
2027                 //Convert the profile to HTML, and pass it to libgaim
2028                 if(profile){
2029                         profileHTML = [self encodedAttributedString:profile forListObject:nil];
2030                 }
2031                 
2032                 [gaimThread setInfo:profileHTML onAccount:self];
2033                 
2034                 //We now have a profile
2035                 [self setStatusObject:profile forKey:@"TextProfile" notify:NotifyNow];
2036         }
2040  * @brief Set our user image
2042  * Pass nil for no image. This resizes and converts the image as needed for our protocol.
2043  * After setting it with gaim, it sets it within Adium; if this is not called, the image will
2044  * show up neither locally nor remotely.
2045  */
2046 - (void)setAccountUserImageData:(NSData *)originalData
2048         NSImage *image =  (originalData ? [[[NSImage alloc] initWithData:originalData] autorelease] : nil);
2049         
2050         if (account) {
2051                 NSSize          imageSize = [image size];
2053                 //Clear the existing icon first
2054                 [gaimThread setBuddyIcon:nil onAccount:self];
2055                 
2056                 /* Now pass libgaim the new icon.  Libgaim takes icons as a file, so we save our
2057                  * image to one, and then pass libgaim the path. Check to be sure our image doesn't have an NSZeroSize size,
2058                  * which would indicate currupt data */
2059                 if (image && !NSEqualSizes(NSZeroSize, imageSize)) {
2060                         GaimPluginProtocolInfo  *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gaim_find_prpl(account->protocol_id));
2061                         GaimDebug(@"Original image of size %f %f",imageSize.width,imageSize.height);
2062                         
2063                         if (prpl_info && (prpl_info->icon_spec.format)) {
2064                                 NSString        *buddyIconFilename = [self _userIconCachePath];
2065                                 NSData          *buddyIconData = nil;
2066                                 BOOL            smallEnough, prplScales;
2067                                 unsigned        i;
2068                                 
2069                                 /* We need to scale it down if:
2070                                  *      1) The prpl needs to scale before it sends to the server or other buddies AND
2071                                  *      2) The image is larger than the maximum size allowed by the protocol
2072                                  * We ignore the minimum required size, as scaling up just leads to pixellated images.
2073                                  */
2074                                 smallEnough =  (prpl_info->icon_spec.max_width >= imageSize.width &&
2075                                                                 prpl_info->icon_spec.max_height >= imageSize.height);
2076                                 
2077                                 prplScales = (prpl_info->icon_spec.scale_rules & GAIM_ICON_SCALE_SEND) || (prpl_info->icon_spec.scale_rules & GAIM_ICON_SCALE_DISPLAY);
2078                                 
2079                                 if (prplScales &&  !smallEnough) {
2080                                         //Determine the scaled size.  If it's too big, scale to the largest permissable size
2081                                         image = [image imageByScalingToSize:NSMakeSize(prpl_info->icon_spec.max_width,
2082                                                                                                                                    prpl_info->icon_spec.max_height)];
2083                                         
2084                                         /* Our original data is no longer valid, since we had to scale to a different size */
2085                                         originalData = nil;
2086                                         GaimDebug(@"Scaled image to size %@",NSStringFromSize([image size]));
2087                                 }
2088                                 
2089                                 if (!buddyIconData) {
2090                                         char            **prpl_formats =  g_strsplit(prpl_info->icon_spec.format,",",0);
2091                                         
2092                                         //Look for gif first if the image is animated
2093                                         NSImageRep      *imageRep = [image bestRepresentationForDevice:nil] ;
2094                                         if ([imageRep isKindOfClass:[NSBitmapImageRep class]] &&
2095                                                 [[(NSBitmapImageRep *)imageRep valueForProperty:NSImageFrameCount] intValue] > 1) {
2096                                                 
2097                                                 for (i = 0; prpl_formats[i]; i++) {
2098                                                         if (strcmp(prpl_formats[i],"gif") == 0) {
2099                                                                 /* Try to use our original data.  If we had to scale, originalData will have been set
2100                                                                  * to nil and we'll continue below to convert the image. */
2101                                                                 GaimDebug(@"l33t script kiddie animated GIF!!111");
2102                                                                 
2103                                                                 buddyIconData = originalData;
2104                                                                 if (buddyIconData)
2105                                                                         break;
2106                                                         }
2107                                                 }
2108                                         }
2109                                         
2110                                         if (!buddyIconData) {
2111                                                 for (i = 0; prpl_formats[i]; i++) {
2112                                                         if (strcmp(prpl_formats[i],"png") == 0) {
2113                                                                 buddyIconData = [image PNGRepresentation];
2114                                                                 if (buddyIconData)
2115                                                                         break;
2116                                                                 
2117                                                         } else if ((strcmp(prpl_formats[i],"jpeg") == 0) || (strcmp(prpl_formats[i],"jpg") == 0)) {
2118                                                                 /* OS X 10.4's JPEG representation does much better than 10.3's.  Unfortunately, that also
2119                                                                  * means larger file sizes... which for our only JPEG-based protocol, AIM, means the buddy
2120                                                                  * icon doesn't get sent.  AIM max is 8 kilobytes; 10.4 produces 12 kb images.  0.90 is
2121                                                                  * large indistinguishable from 1.0 anyways.
2122                                                                  */
2123                                                                 float compressionFactor = ([NSApp isOnTigerOrBetter] ?
2124                                                                                                                    0.9 :
2125                                                                                                                    1.0);
2126                                                                 
2127                                                                 buddyIconData = [image JPEGRepresentationWithCompressionFactor:compressionFactor];
2128                                                                 if (buddyIconData)
2129                                                                         break;
2130                                                                 
2131                                                         } else if ((strcmp(prpl_formats[i],"tiff") == 0) || (strcmp(prpl_formats[i],"tif") == 0)) {
2132                                                                 buddyIconData = [image TIFFRepresentation];
2133                                                                 if (buddyIconData)
2134                                                                         break;
2135                                                                 
2136                                                         } else if (strcmp(prpl_formats[i],"gif") == 0) {
2137                                                                 buddyIconData = [image GIFRepresentation];
2138                                                                 if (buddyIconData)
2139                                                                         break;
2140                                                                 
2141                                                         } else if (strcmp(prpl_formats[i],"bmp") == 0) {
2142                                                                 buddyIconData = [image BMPRepresentation];
2143                                                                 if (buddyIconData)
2144                                                                         break;
2145                                                                 
2146                                                         }                                               
2147                                                 }
2148                                         }
2149                                         
2150                                         //Cleanup
2151                                         g_strfreev(prpl_formats);
2152                                 }
2153                                 
2154                                 if ([buddyIconData writeToFile:buddyIconFilename atomically:YES]) {
2155                                         [gaimThread setBuddyIcon:buddyIconFilename onAccount:self];
2156                                         
2157                                 } else {
2158                                         GaimDebug(@"Error writing file %@",buddyIconFilename);   
2159                                 }
2160                         }
2161                 }
2162         }
2163         
2164         //We now have an icon
2165         [self setStatusObject:image forKey:KEY_USER_ICON notify:NotifyNow];
2168 #pragma mark Group Chat
2169 - (BOOL)inviteContact:(AIListContact *)inContact toChat:(AIChat *)inChat withMessage:(NSString *)inviteMessage
2171         [gaimThread inviteContact:inContact toChat:inChat withMessage:inviteMessage];
2172         
2173         return YES;
2176 #pragma mark Buddy Menu Items
2177 //Returns an array of menuItems specific for this contact based on its account and potentially status
2178 - (NSArray *)menuItemsForContact:(AIListContact *)inContact
2180         NSMutableArray                  *menuItemArray = nil;
2181         if (account && gaim_account_is_connected(account)){
2182                 GaimPluginProtocolInfo  *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
2183                 GList                                   *l, *ll;
2184                 
2185                 GaimBuddy                               *buddy;
2186                 
2187                 //Find the GaimBuddy
2188                 buddy = gaim_find_buddy(account, gaim_normalize(account, [[inContact UID] UTF8String]));
2189                 
2190                 if(prpl_info && prpl_info->blist_node_menu && buddy){
2191                         NSImage *serviceIcon = [AIServiceIcons serviceIconForService:[self service]
2192                                                                                                                                         type:AIServiceIconSmall
2193                                                                                                                            direction:AIIconNormal];
2194                         
2195                         //Add a NSMenuItem for each node action specified by the prpl
2196                         for(l = ll = prpl_info->blist_node_menu((GaimBlistNode *)buddy); l; l = l->next) {
2197                                 GaimBlistNodeAction *act = (GaimBlistNodeAction *) l->data;
2198                                 NSDictionary            *dict;
2199                                 NSMenuItem                      *menuItem;
2200                                 NSString                        *title;
2201                                 
2202                                 //If titleForContactMenuLabel:forContact: returns nil, we don't add the menuItem
2203                                 if (act &&
2204                                         act->label &&
2205                                         (title = [self titleForContactMenuLabel:act->label
2206                                                                                                  forContact:inContact])) { 
2207                                         menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
2208                                                                                                                                                                          target:self
2209                                                                                                                                                                          action:@selector(performContactMenuAction:)
2210                                                                                                                                                           keyEquivalent:@""] autorelease];
2211                                         [menuItem setImage:serviceIcon];
2212                                         dict = [NSDictionary dictionaryWithObjectsAndKeys:
2213                                                 [NSValue valueWithPointer:act],@"GaimBlistNodeAction",
2214                                                 [NSValue valueWithPointer:buddy],@"GaimBuddy",
2215                                                 nil];
2216                                         
2217                                         if(!menuItemArray) menuItemArray = [NSMutableArray array];
2219                                         [menuItem setRepresentedObject:dict];
2220                                         [menuItemArray addObject:menuItem];
2221                                 }
2222                         }
2223                         g_list_free(ll);
2224                 }
2225         }
2226         
2227         return(menuItemArray);
2230 //Action of a dynamically-generated contact menu item
2231 - (void)performContactMenuAction:(NSMenuItem *)sender
2233         NSDictionary            *dict = [sender representedObject];
2234         
2235         [gaimThread performContactMenuActionFromDict:dict];
2238 //Subclasses may override to provide a localized label and/or prevent a specified label from being shown
2239 - (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
2241         return([NSString stringWithUTF8String:label]);
2245 * @brief Menu items for the account's actions
2247  * Returns an array of menu items for account-specific actions.  This is the best place to add protocol-specific
2248  * actions that aren't otherwise supported by Adium.  It will only be queried if the account is online.
2249  * @return NSArray of NSMenuItem instances for this account
2250  */
2251 - (NSArray *)accountActionMenuItems
2253         NSMutableArray                  *menuItemArray = nil;
2254         
2255         if (account && gaim_account_is_connected(account)){
2256                 GaimPlugin *plugin = account->gc->prpl;
2257                 
2258                 if(GAIM_PLUGIN_HAS_ACTIONS(plugin)){
2259                         GList   *l, *ll;
2260                         
2261                         //Avoid adding separators between nonexistant items (i.e. items which Gaim shows but we don't)
2262                         BOOL    addedAnAction = NO;
2263                         NSImage *serviceIcon = [AIServiceIcons serviceIconForService:[self service]
2264                                                                                                                                         type:AIServiceIconSmall
2265                                                                                                                            direction:AIIconNormal];
2266                         for (l = ll = GAIM_PLUGIN_ACTIONS(plugin, account->gc); l; l = l->next) {
2267                                 
2268                                 if(l->data){
2269                                         GaimPluginAction        *action;
2270                                         NSDictionary            *dict;
2271                                         NSMenuItem                      *menuItem;
2272                                         NSString                        *title;
2273                                         
2274                                         action = (GaimPluginAction *) l->data;
2275                                         
2276                                         //If titleForAccountActionMenuLabel: returns nil, we don't add the menuItem
2277                                         if (action &&
2278                                                 action->label &&
2279                                                 (title = [self titleForAccountActionMenuLabel:action->label])) {
2281                                                 action->plugin = plugin;
2282                                                 action->context = account->gc;
2284                                                 menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
2285                                                                                                                                                                                  target:self
2286                                                                                                                                                                                  action:@selector(performAccountMenuAction:)
2287                                                                                                                                                                   keyEquivalent:@""] autorelease];
2288                                                 [menuItem setImage:serviceIcon];
2289                                                 dict = [NSDictionary dictionaryWithObject:[NSValue valueWithPointer:action]
2290                                                                                                                    forKey:@"GaimPluginAction"];
2291                                                 
2292                                                 [menuItem setRepresentedObject:dict];
2293                                                 
2294                                                 if(!menuItemArray) menuItemArray = [NSMutableArray array];
2295                                                 
2296                                                 [menuItemArray addObject:menuItem];
2297                                                 addedAnAction = YES;
2298                                         }else{
2299                                                 g_free(action);
2300                                         }
2301                                         
2302                                 }else{
2303                                         if(addedAnAction){
2304                                                 [menuItemArray addObject:[NSMenuItem separatorItem]];
2305                                                 addedAnAction = NO;
2306                                         }
2307                                 }
2308                         } /* end for */
2309                         
2310                         g_list_free(ll);
2311                 }
2312         }
2314         return menuItemArray;
2317 //Action of a dynamically-generated contact menu item
2318 - (void)performAccountMenuAction:(NSMenuItem *)sender
2320         NSDictionary            *dict = [sender representedObject];
2321         
2322         [gaimThread performAccountMenuActionFromDict:dict];
2325 //Subclasses may override to provide a localized label and/or prevent a specified label from being shown
2326 - (NSString *)titleForAccountActionMenuLabel:(const char *)label
2328         if((strcmp(label, "Change Password...") == 0) || (strcmp(label, "Change Password") == 0)){
2329                 /* XXX This depends upon an implementation of adiumGaimRequestFields in adiumGaimRequest.m.
2330                 * Enable once that is done. */
2331                 return(nil);
2332         }
2334         return([NSString stringWithUTF8String:label]);
2337 /* Secure messaging */
2338 #pragma mark Secure Messaging
2339 - (void)requestSecureMessaging:(BOOL)inSecureMessaging
2340                                                 inChat:(AIChat *)inChat
2342         [gaimThread requestSecureMessaging:inSecureMessaging
2343                                                                 inChat:inChat];
2346 - (void)promptToVerifyEncryptionIdentityInChat:(AIChat *)inChat
2348         [gaimThread promptToVerifyEncryptionIdentityInChat:inChat];     
2351 - (BOOL)allowSecureMessagingTogglingForChat:(AIChat *)inChat
2353         //Allow secure messaging via OTR for one-on-one chats
2354         return([inChat name] == nil);
2357 - (NSString *)aboutEncryption
2359         return([NSString stringWithFormat:
2360                 AILocalizedString(@"Adium provides encryption, authentication, deniability, and perfect forward secrecy over %@ via Off-the-Record Messaging (OTR). If your contact is not using an OTR-compatible messaging system, your contact will be sent a link to the OTR web site when you attempt to connect. For more information on OTR, visit http://www.cypherpunks.ca/otr/.",nil),
2361                 [[self service] shortDescription]]);
2364 /********************************/
2365 /* AIAccount subclassed methods */
2366 /********************************/
2367 #pragma mark AIAccount Subclassed Methods
2368 - (void)initAccount
2370         NSDictionary    *defaults = [NSDictionary dictionaryNamed:[NSString stringWithFormat:@"GaimDefaults%@",[[self service] serviceID]]
2371                                                                                                          forClass:[self class]];
2372         
2373         if(defaults){
2374                 [[adium preferenceController] registerDefaults:defaults
2375                                                                                           forGroup:GROUP_ACCOUNT_STATUS
2376                                                                                                 object:self];
2377         }else{
2378                 GaimDebug(@"Failed to load defaults for %@",[NSString stringWithFormat:@"GaimDefaults%@",[[self service] serviceID]]);
2379         }
2380         
2381         //Defaults
2382     reconnectAttemptsRemaining = RECONNECTION_ATTEMPTS;
2383         lastDisconnectionError = nil;
2384         
2385         permittedContactsArray = [[NSMutableArray alloc] init];
2386         deniedContactsArray = [[NSMutableArray alloc] init];
2387         
2388         //We will create a gaimAccount the first time we attempt to connect
2389         account = NULL;
2391         //Observe preferences changes
2392         [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_ALIASES];
2396 * @brief The account's UID changed
2397  */
2398 - (void)didChangeUID
2400         //Only need to take action if we have a created GaimAccount already
2401         if(account != NULL){
2402                 //Remove our current account
2403                 [gaimThread removeAdiumAccount:self];
2404                 
2405                 //Clear the reference to the GaimAccount... it'll be created when needed
2406                 account = NULL;
2407         }
2410 - (void)dealloc
2411 {       
2412         [[adium preferenceController] unregisterPreferenceObserver:self];
2414         [lastDisconnectionError release]; lastDisconnectionError = nil;
2415                 
2416         [permittedContactsArray release];
2417         [deniedContactsArray release];
2418         
2419     [super dealloc];
2422 - (NSString *)unknownGroupName {
2423     return (@"Unknown");
2426 - (NSDictionary *)defaultProperties { return([NSDictionary dictionary]); }
2428 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
2430         return [inAttributedString string]; //Default behavior is plain text
2433 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject contentMessage:(AIContentMessage *)contentMessage
2435         return [self encodedAttributedString:inAttributedString forListObject:inListObject];
2438 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forGaimStatusType:(const char *)gaimStatusType
2440         return [self encodedAttributedString:inAttributedString forListObject:nil];     
2443 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
2444                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
2446         [super preferencesChangedForGroup:group key:key object:object preferenceDict:prefDict firstTime:firstTime];
2448         if([group isEqualToString:PREF_GROUP_ALIASES]){
2449                 //If the notification object is a listContact belonging to this account, update the serverside information
2450                 if ((account != nil) && 
2451                         ([self shouldSetAliasesServerside]) &&
2452                         ([key isEqualToString:@"Alias"])){
2454                         NSString *alias = [object preferenceForKey:@"Alias"
2455                                                                                                  group:PREF_GROUP_ALIASES 
2456                                                                  ignoreInheritedValues:YES];
2458                         if([object isKindOfClass:[AIMetaContact class]]){
2459                                 NSEnumerator    *enumerator = [[(AIMetaContact *)object containedObjects] objectEnumerator];
2460                                 AIListContact   *containedListContact;
2461                                 while(containedListContact = [enumerator nextObject]){
2462                                         if([containedListContact account] == self){
2463                                                 [gaimThread setAlias:alias forUID:[containedListContact UID] onAccount:self];
2464                                         }
2465                                 }
2466                                 
2467                         }else if([object isKindOfClass:[AIListContact class]]){
2468                                 if([(AIListContact *)object account] == self){
2469                                         [gaimThread setAlias:alias forUID:[object UID] onAccount:self];
2470                                 }
2471                         }
2472                 }
2473                 
2474         }
2477 /***************************/
2478 /* Account private methods */
2479 /***************************/
2480 #pragma mark Private
2481 - (void)setTypingFlagOfChat:(AIChat *)chat to:(NSNumber *)typingStateNumber
2483     AITypingState currentTypingState = [chat integerStatusObjectForKey:KEY_TYPING];
2484         AITypingState newTypingState = [typingStateNumber intValue];
2486     if(currentTypingState != newTypingState){
2487                 [chat setStatusObject:(newTypingState ? typingStateNumber : nil)
2488                                            forKey:KEY_TYPING
2489                                            notify:NotifyNow];
2490     }
2493 - (void)displayError:(NSString *)errorDesc
2495     [[adium interfaceController] handleErrorMessage:[NSString stringWithFormat:@"%@ (%@) : Gaim error",[self UID],[[self service] shortDescription]]
2496                                     withDescription:errorDesc];
2499 - (NSString *)_userIconCachePath
2500 {    
2501     NSString    *userIconCacheFilename = [NSString stringWithFormat:@"TEMP-UserIcon_%@_%@", [self internalObjectID], [NSString randomStringOfLength:4]];
2502     return([[adium cachesPath] stringByAppendingPathComponent:userIconCacheFilename]);
2505 - (AIListContact *)contactWithUID:(NSString *)inUID
2507         return [super contactWithUID:inUID];
2510 - (AIListContact *)mainThreadContactWithUID:(NSString *)inUID
2512         AIListContact   *contact;
2514         contact = [self mainPerformSelector:@selector(contactWithUID:)
2515                                                          withObject:inUID
2516                                                         returnValue:YES];
2518         return(contact);
2521 - (NSNumber *)shouldCheckMail
2523         return([self preferenceForKey:KEY_ACCOUNT_CHECK_MAIL group:GROUP_ACCOUNT_STATUS]);
2526 - (BOOL)displayConversationClosed
2528         return(NO);
2531 - (BOOL)displayConversationTimedOut
2533         return(NO);
2536 - (BOOL)shouldSetAliasesServerside
2538         return(NO);
2541 - (NSString *)internalObjectID
2543         return([super internalObjectID]);
2546 @end